package com.bsoft.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.*;

/**
 * 国密加密工具类 (SM2/SM4)
 * 遵循文档中请求加密流程
 * 新津调用三方专病接口 加密
 */
@Slf4j
public class GmCryptoUtils {

    private static final OkHttpClient httpClient = new OkHttpClient();
    private static final String API_URL = "http://10.190.122.119:30079/idc-basic-api-xinjin";

    static {
        // [文档 4.] 添加BouncyCastle作为加解密库的Provider
        Security.addProvider(new BouncyCastleProvider());
    }


    /*国密加密*/
    public static Map<String, Object> commEncrypt(String encryptData, String urlApi) throws Exception {
        // [文档 2.1] 步骤0: 动态获取公钥
        String publicKeyHex = getPublicKey(API_URL);
        if (publicKeyHex == null || publicKeyHex.isEmpty()) {
            throw new Exception("获取专病公钥失败!");
        }
        log.info("\n--- 1. [文档 3.1] 开始加密流程 ---");

        // [文档 3.1.1] 1. 生成16位密钥种子
        String seedString = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        // [文档 3.1.1] 2. 将密钥种子转换为32位十六进制 实际SM4密钥
        String actualKeyHex = stringToHexJsStyle(seedString);

        // [文档 3.1.2] SM2加密临时密钥
        // SM2的明文是上一步生成的 actualKeyHex (32字符的十六进制字符串 实际SM4密钥)
        // 经过调试验证，BouncyCastle的C1C3C2+未压缩模式生成的密文(以04开头)是服务端唯一能正确解密的格式。
        // 因此，文档中“规避04开头密文”的要求已过时或不适用于当前服务端版本，代码中不再需要该循环逻辑。
        String sm2EncryptedKeyHex = sm2EncryptString(actualKeyHex, publicKeyHex);
        log.info("使用的密钥种子:{}->使用的实际SM4密钥:{}->SM2加密后的密钥 (未压缩, C1C3C2 格式Hex):{}", seedString, actualKeyHex, sm2EncryptedKeyHex);
        // [文档 3.1.3] SM4加密业务数据
        String sm4EncryptedDataHex = GmCryptoUtils.sm4Encrypt(encryptData, actualKeyHex);
        log.info("   [+] SM4加密后的业务数据 (Hex): " + sm4EncryptedDataHex);

        String keyBase64 = Base64.getEncoder().encodeToString(sm2EncryptedKeyHex.getBytes(StandardCharsets.UTF_8));

        JSONObject reqData = new JSONObject();
        reqData.put("key", keyBase64);
        reqData.put("data", sm4EncryptedDataHex);

        //构造请求头
        Map<String, Object> headerMap = constructReqHeader(encryptData, urlApi);
        String url = constructReqUrl(urlApi);
        //发送请求
        Map<String,Object> resData = HttpClientUtil.sendPostByJson(url, headerMap, reqData, StandardCharsets.UTF_8);
        log.info("加密:请求调用返回:" + JSONObject.toJSONString(resData));
        //进行解密
        if (!"200".equals(resData.get("code").toString())){
            throw new Exception("调用专病["+urlApi+"]:接口返回失败!");
        }
        String responseXDataTime = resData.get("X-Data-Time").toString();
        String responseBodyString = resData.get("data") != null ? resData.get("data").toString() : "";
        log.info("[+] 原始响应头 (X-Data-Time): " + responseXDataTime + "   [+] 原始响应体: " + responseBodyString);
        //进入解密
        String decDataStr = GmCryptoUtils.commDecrypt(publicKeyHex, responseXDataTime, responseBodyString);

        Map<String, Object> decResMap = new HashMap<>();
        //判断字符串是否是数组
        if (decDataStr.startsWith("[") || decDataStr.endsWith("]")){
            decResMap.put("list", JSONArray.parseArray(decDataStr));
        }else {
            decResMap = JSONUtil.toHashMap(decDataStr);
        }
        log.info("最终返回解密后参数:"+decDataStr);
        return decResMap;
    }

    /*国密解密*/
    public static String commDecrypt(String publicKeyHex,String responseXDataTime,String encryptedResponseData) throws Exception {
        log.info("\n--- 3. [文档 3.2] 开始解密流程 ---");
        // [文档 3.2.1 & 5.3] 重构SM4密钥
        String reconstructedSeedString = buildSM4Secret(publicKeyHex, responseXDataTime);
        // [文档 3.2.2] 将重构的密钥种子转换为实际的十六进制密钥
        String reconstructedActualKeyHex = stringToHexJsStyle(reconstructedSeedString);
        log.info("解密--> [+] 重构的密钥种子: " + reconstructedSeedString + "  [+] 重构的实际SM4密钥: " + reconstructedActualKeyHex);
        //解密业务数据
        String decryptedDataString = sm4Decrypt(encryptedResponseData, reconstructedActualKeyHex);
        log.info("4.最终解密结果:"+decryptedDataString);
        return decryptedDataString;
    }

    /*构造请求头*/
    public static Map<String, Object> constructReqHeader(String reqData, String reqPath) throws Exception {
        // [文档 3.1.5] 构造HTTP头
        String timestamp = String.valueOf(System.currentTimeMillis());
        // [文档 3.1.5 修正] 根据JS Demo，source_string 由 path + 未加密的body构成
        String sourceString = reqPath + reqData;
        String hash = md5(sourceString);
        String xDataTime = timestamp + hash;

        Map<String, Object> headerMap = new LinkedHashMap<>();
        headerMap.put("x1-gp-color", "gray");
        headerMap.put("X-Is-Safety", "1");
        headerMap.put("X-Data-Time", xDataTime);
        log.info("   [+] 生成的HTTP头:" + JSONUtil.map2Json(headerMap));
        return headerMap;
    }

    /*构造请求地址*/
    public static String constructReqUrl(String reqPath) throws Exception {
        String url = API_URL + reqPath;
        log.info("   [+] 生成的请求地址:" + url);
        return url;
    }


    /**
     * [文档 2.1] 从服务器获取SM2公钥
     */
    private static String getPublicKey(String baseUrl) throws IOException {
        String keyApiUrl = baseUrl + "/api/gupo-crypto/crypto/get-public-key";
        log.info("--- 0. 从 " + keyApiUrl + " 获取公钥 ---");
        Request request = new Request.Builder().url(keyApiUrl).post(RequestBody.create(new byte[0])).addHeader("x1-gp-color", "gray").build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) throw new IOException("HTTP请求失败，状态码: " + response.code());
            String responseBody = response.body().string();
            Map<String, Object> gyResMap = JSONUtil.toHashMap(responseBody);
            if (!"200.0".equals(gyResMap.get("code").toString())) {
                throw new RuntimeException("API响应格式不正确: " + responseBody);
            }
            String publicKey = ((Map<String, Object>) gyResMap.get("data")).get("public_key").toString();
            log.info(" [+] 成功获取公钥");
            return publicKey;
        } catch (Exception e) {
            log.error("   [!] 获取公钥时发生严重错误: " + e.getMessage());
            throw e;
        }
    }

    /**
     * [文档 3.1.1] 模拟 JavaScript 的 string.charCodeAt(i).toString(16) 行为，将字符串转为Hex。
     */
    public static String stringToHexJsStyle(String str) {
        if (str == null) return "";
        StringBuilder hexString = new StringBuilder();
        for (char c : str.toCharArray()) {
            hexString.append(Integer.toHexString(c));
        }
        return hexString.toString();
    }

    /**
     * [文档 3.1.2] 使用SM2公钥加密字符串明文。
     *
     * @param plainText    要加密的字符串（此处为32位的actualKeyHex）
     * @param publicKeyHex SM2公钥的十六进制表示
     * @return 十六进制格式的密文
     */
    public static String sm2EncryptString(String plainText, String publicKeyHex) {
        try {
            // 将要加密的字符串转换为UTF-8字节
            byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
            byte[] publicKeyBytes = Hex.decodeHex(publicKeyHex);

            X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
            ECDomainParameters domainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
            ECPoint ecPoint = x9ECParameters.getCurve().decodePoint(publicKeyBytes);
            ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(ecPoint, domainParameters);

            // 设置为 C1C3C2 模式，与 sm-crypto JS库的 cipherMode: 1 (默认) 行为保持一致
            SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
            sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

            byte[] encryptedBytes = sm2Engine.processBlock(plainTextBytes, 0, plainTextBytes.length);

            // 直接返回BouncyCastle在此模式下的标准输出 (未压缩点格式)
            return Hex.encodeHexString(encryptedBytes);
        } catch (Exception e) {
            throw new RuntimeException("SM2加密失败", e);
        }
    }

    /**
     * [文档 3.1.3] SM4加密业务数据
     */
    public static String sm4Encrypt(String plainText, String keyHex) {
        try {
            byte[] keyBytes = Hex.decodeHex(keyHex);
            byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
            // [文档 4.] 使用SM4/ECB/PKCS7Padding模式
            Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encryptedBytes = cipher.doFinal(plainTextBytes);
            return Hex.encodeHexString(encryptedBytes);
        } catch (Exception e) {
            throw new RuntimeException("SM4加密失败", e);
        }
    }

    /**
     * [文档 3.1.5] MD5哈希算法
     */
    public static String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest(input.getBytes());
            BigInteger no = new BigInteger(1, messageDigest);
            String hashtext = no.toString(16);
            while (hashtext.length() < 32) {
                hashtext = "0" + hashtext;
            }
            return hashtext;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * [文档 3.2.1] 根据公钥和时间戳重构SM4密钥种子的算法
     */
    public static String buildSM4Secret(String key, String time) {
        final int MEDIAN = 5;
        final int AFT = 1;
        final int BEF = -1;
        final int FACTOR_2 = 2;
        final int FACTOR_1 = 1;
        final int KEY_LENGTH = 16;
        int method = Integer.parseInt(time.substring(time.length() - 2, time.length() - 1)) > 5 ? AFT : BEF;
        int offsetNum = Integer.parseInt(time.substring(time.length() - 1));
        int factor = Math.abs(offsetNum - method) > MEDIAN ? FACTOR_2 : FACTOR_1;
        int offset = offsetNum * factor * method;
        String stringToHash = (key + time).toLowerCase();
        String md5Hash = md5(stringToHash);
        String _key;
        if (offset >= 0) {
            _key = md5Hash.substring(offset, Math.min(offset + KEY_LENGTH, md5Hash.length()));
        } else {
            _key = md5Hash.substring(md5Hash.length() + offset, Math.min(md5Hash.length() + offset + KEY_LENGTH, md5Hash.length()));
        }
        while (_key.length() < KEY_LENGTH) {
            _key += md5Hash.substring(0, KEY_LENGTH - _key.length());
        }
        return _key;
    }

    /**
     * [文档 3.2.2] SM4解密业务数据
     */
    public static String sm4Decrypt(String encryptedHex, String keyHex) {
        try {
            byte[] keyBytes = Hex.decodeHex(keyHex);
            byte[] encryptedBytes = Hex.decodeHex(encryptedHex);
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
            // [文档 4.] 使用SM4/ECB/PKCS7Padding模式
            Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("SM4解密失败", e);
        }
    }

    public static String getMD5Str(String str) {
        byte[] digest = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("md5");
            digest = md5.digest(str.getBytes("utf-8"));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //16是表示转换为16进制数
        String md5Str = new BigInteger(1, digest).toString(16);
        return md5Str;
    }

}